package cd

import (
	"context"
	"fmt"
	"go-admin/app/admin/cicd/ci"
	appsv1 "k8s.io/api/apps/v1"
	apiv1 "k8s.io/api/core/v1"
	"k8s.io/apimachinery/pkg/api/resource"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/util/intstr"
	"k8s.io/client-go/kubernetes"
	"k8s.io/client-go/tools/clientcmd"
	"strconv"
)

type Client struct {
	clientSet *kubernetes.Clientset
}

func int32Ptr(i int32) *int32 { return &i }

func New(cfg []byte) (*Client, error) {
	config, err := clientcmd.RESTConfigFromKubeConfig(cfg)
	if err != nil {
		fmt.Println("创建rest config失败")
		return nil, err
	}
	clientset, err := kubernetes.NewForConfig(config)
	if err != nil {
		fmt.Println("创建clientset 失败")
		return nil, err
	}
	return &Client{clientSet: clientset}, nil
}

func (c *Client) CreateDeployment(options *ci.PipelineOptions) error {
	replicas := int32Ptr(1)
	if options.Namespace == "prod" {
		replicas = int32Ptr(2)
	}

	deployment := &appsv1.Deployment{
		ObjectMeta: metav1.ObjectMeta{
			Name: options.AppName,
		},
		Spec: appsv1.DeploymentSpec{
			Replicas: replicas,
			Selector: &metav1.LabelSelector{
				MatchLabels: map[string]string{
					"app": options.AppName,
				},
			},
			Template: apiv1.PodTemplateSpec{
				ObjectMeta: metav1.ObjectMeta{
					Labels: map[string]string{
						"app": options.AppName,
					},
				},
				Spec: apiv1.PodSpec{
					ImagePullSecrets: []apiv1.LocalObjectReference{
						{
							Name: "maka",
						},
					},
					Containers: []apiv1.Container{
						{
							Name:            options.AppName,
							Image:           fmt.Sprintf("%s/%s/%s", options.ImageUrl, options.ImageProject, options.AppName),
							ImagePullPolicy: "IfNotPresent",
						},
					},
				},
			},
		},
	}

	env := []apiv1.EnvVar{
		{
			Name:  "TZ",
			Value: "Asia/Shanghai",
		},
	}

	if options.ApolloConfigURL != "" {
		service := apiv1.EnvVar{
			Name:  "SERVICE_NAME",
			Value: options.AppName,
		}

		if options.ApolloServiceName != "" {
			service.Value = options.ApolloServiceName
		}

		url := apiv1.EnvVar{
			Name:  "APOLLO_CONFIG_URL",
			Value: options.ApolloConfigURL,
		}

		ns := apiv1.EnvVar{
			Name:  "NAMESPACE",
			Value: options.Namespace,
		}

		env = append(env, service, url, ns)
	}

	deployment.Spec.Template.Spec.Containers[0].Env = env

	request := map[apiv1.ResourceName]resource.Quantity{}
	limit := map[apiv1.ResourceName]resource.Quantity{}

	if options.CpuRequest != "" {
		quantity, err := resource.ParseQuantity(options.CpuRequest)
		if err != nil {
			return err
		}
		request["cpu"] = quantity
	}

	if options.MemRequest != "" {
		quantity, err := resource.ParseQuantity(options.MemRequest)
		if err != nil {
			return err
		}
		request["memory"] = quantity
	}

	if options.CpuLimit != "" {
		quantity, err := resource.ParseQuantity(options.CpuLimit)
		if err != nil {
			return err
		}
		limit["cpu"] = quantity
	}

	if options.MemLimit != "" {
		quantity, err := resource.ParseQuantity(options.MemLimit)
		if err != nil {
			return err
		}
		limit["memory"] = quantity
	}

	podResource := apiv1.ResourceRequirements{
		Requests: request,
		Limits:   limit,
	}

	deployment.Spec.Template.Spec.Containers[0].Resources = podResource

	if options.Port != "" {
		port, err := strconv.Atoi(options.Port)
		if err != nil {
			return err
		}
		prob := &apiv1.Probe{
			ProbeHandler: apiv1.ProbeHandler{
				TCPSocket: &apiv1.TCPSocketAction{
					Port: intstr.FromInt(port),
				},
			},
			InitialDelaySeconds: 10,
			TimeoutSeconds:      3,
			PeriodSeconds:       10,
			SuccessThreshold:    1,
			FailureThreshold:    3,
		}
		ports := []apiv1.ContainerPort{
			{
				Name:          "http",
				Protocol:      apiv1.ProtocolTCP,
				ContainerPort: int32(port),
			},
		}

		deployment.Spec.Template.Spec.Containers[0].Ports = ports
		deployment.Spec.Template.Spec.Containers[0].LivenessProbe = prob
		deployment.Spec.Template.Spec.Containers[0].ReadinessProbe = prob
	}

	if _, err := c.clientSet.AppsV1().Deployments(options.Namespace).Create(context.TODO(), deployment, metav1.CreateOptions{}); err != nil {
		return err
	}
	return nil
}

func (c *Client) CreateService(options *ci.PipelineOptions) error {
	if options.Port == "" {
		return nil
	}
	port, err := strconv.Atoi(options.Port)
	if err != nil {
		return err
	}
	svc := &apiv1.Service{ObjectMeta: metav1.ObjectMeta{
		Name:      options.AppName,
		Namespace: options.Namespace,
		Labels:    nil,
	}, Spec: apiv1.ServiceSpec{
		Type: apiv1.ServiceTypeClusterIP,
		Selector: map[string]string{
			"app": options.AppName,
		},
		Ports: []apiv1.ServicePort{
			{
				Name:       "http",
				Port:       int32(port),
				Protocol:   apiv1.ProtocolTCP,
				TargetPort: intstr.FromInt(port),
			},
		},
	}}

	if _, err := c.clientSet.CoreV1().Services(options.Namespace).Create(context.TODO(), svc, metav1.CreateOptions{}); err != nil {
		return err
	}
	return nil
}
